#include <stdlib.h>
/* debug */
struct data {
  char trace_ascii; /* 1 or 0 */ 
};
#include <strings.h>
#include <curl/curl.h>
#include "gauthenticate.h"
#include <time.h>
#include <stdarg.h>

static int check_request_error(gservice *gcalobj, int code,
			       int expected_answer);
static void reset_buffer(gservice *ptr);
static int common_upload(gservice *gcalobj,
			 char *header, char *header2, char *header3,
			 char *header4,
			 struct curl_slist **curl_headers,
			 const char *gdata_version);
static size_t write_cb(void *ptr, size_t count, size_t chunk_size, void *data);

struct remoteFile {
	const char *filename;
	FILE *stream;
};

static size_t write_file(void *buffer, size_t size, size_t nmemb, void *data);

static size_t write_file(void *buffer, size_t size, size_t nmemb, void *stream)
{
  struct remoteFile *out=(struct remoteFile *)stream;

  if(out && !out->stream) {
    /* open file for writing */ 
    out->stream=fopen(out->filename, "wb");
    if(!out->stream)
      return -1; /* failure, can't open file to write */ 
  }
  return fwrite(buffer, size, nmemb, out->stream);
}

static size_t write_cb(void *ptr, size_t count, size_t chunk_size, void *data)
{

	size_t size = count * chunk_size;
	gservice *gcal_ptr = (gservice *)data;
	int current_length = strlen(gcal_ptr->buffer);
	char *ptr_tmp;

	if (size > (gcal_ptr->length - current_length - 1)) {
		gcal_ptr->length = current_length + size + 1;
		/* TODO: is it save to continue reallocing more memory?
		 * what happens if the gcalendar list is *really* big?
		 * how big can it be? Maybe I should use another write
		 * callback
		 * when requesting the Atom feed (one that will treat the
		 * the stream as its being read and not store it in memory).
		 */
		ptr_tmp = realloc(gcal_ptr->buffer, gcal_ptr->length);

		if (!ptr_tmp) {
			if (gcal_ptr->fout_log)
				fprintf(gcal_ptr->fout_log,
					"write_cb: Failed relloc!\n");
			goto exit;
		}

		gcal_ptr->buffer = ptr_tmp;
	}

	strncat(gcal_ptr->buffer, (char *)ptr, size);

exit:
	return size;
}
static int common_upload(gservice *gcalobj,
			 char *header, char *header2, char *header3,
			 char *header4,
			 struct curl_slist **curl_headers,
			 const char *gdata_version)
{
	int result = -1;
	CURL *curl_ctx = gcalobj->curl;
	struct curl_slist *response_headers = NULL;

	/* To support Google Data API 2.0 */
        if(gdata_version)
	  response_headers = curl_slist_append(response_headers,
					     gdata_version);

	if (header)
		response_headers = curl_slist_append(response_headers, header);
	if (header2)
		response_headers = curl_slist_append(response_headers, header2);
	if (header3)
		response_headers = curl_slist_append(response_headers, header3);
	if (header4)
		response_headers = curl_slist_append(response_headers, header4);

	if (!response_headers)
		return result;

	*curl_headers = response_headers;

	curl_easy_setopt(curl_ctx, CURLOPT_HTTPHEADER, response_headers);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, write_cb);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, (void *)gcalobj);

	return result = 0;
}


void gcal_destroy(gservice *gcal_obj) {
	if (!gcal_obj)
		return;

	if (gcal_obj->buffer)
		free(gcal_obj->buffer);
	if (gcal_obj->curl)
		curl_easy_cleanup(gcal_obj->curl);
	if (gcal_obj->auth)
		free(gcal_obj->auth);
	if (gcal_obj->url)
		free(gcal_obj->url);
	if (gcal_obj->user)
		free(gcal_obj->user);
	//if (gcal_obj->document) clean_dom_document(gcal_obj->document);
	if (gcal_obj->curl_msg)
		free(gcal_obj->curl_msg);
	if (gcal_obj->fout_log)
		fclose(gcal_obj->fout_log);
	if (gcal_obj->max_results)
		free(gcal_obj->max_results);
	if (gcal_obj->timezone)
		free(gcal_obj->timezone);
	if (gcal_obj->location)
		free(gcal_obj->location);
	if (gcal_obj->domain)
		free(gcal_obj->domain);

	free(gcal_obj);
}

gservice *gcal_construct(int service) {
	gservice *ptr;
	ptr = malloc(sizeof(gservice));
	if (!ptr)
		goto exit;

	ptr->has_xml = 0;
	ptr->document = NULL;
	ptr->user = NULL;
	ptr->domain = NULL;
	ptr->url = NULL;
	ptr->auth = NULL;
	ptr->buffer = NULL;
	reset_buffer(ptr);
	ptr->curl = curl_easy_init();
        curl_easy_setopt(ptr->curl, CURLOPT_CRLF, 0);
	ptr->http_code = 0;
	ptr->curl_msg = NULL;
	ptr->http_code = 0;
	ptr->internal_status = 0;
	ptr->fout_log = NULL;
	ptr->max_results = strdup(GCAL_UPPER);
	ptr->timezone = NULL;
	ptr->location = NULL;
	ptr->deleted = 0;
	ptr->store_xml_entry = 0;
        ptr->service = "cloudprint";

	if (!(ptr->buffer) || (!(ptr->curl)) || (!ptr->max_results)) {
		if (ptr->max_results)
			free(ptr->max_results);
		gcal_destroy(ptr);
		ptr = NULL;
		goto exit;
	}

exit:
	return ptr;
}

static void reset_buffer(gservice *ptr) {
	if (ptr->buffer)
		free(ptr->buffer);
	ptr->length = 256;
	ptr->buffer = (char *) calloc(ptr->length, sizeof(char));
	ptr->previous_length = 0;
}


int load_vheaders(gservice *svc, struct curl_slist **curl_headers, const char *hdr_array[], va_list argp) {
        char *hdr;
	int result = -1, i;
	CURL *curl_ctx = svc->curl;
	struct curl_slist *response_headers = NULL;

        response_headers = curl_slist_append(response_headers, "Accept:");
        for(i=0; hdr_array[i] != NULL; i++) 
		response_headers = curl_slist_append(response_headers, hdr_array[i]);
        
        while((hdr = va_arg(argp, char*)) != NULL) {
		response_headers = curl_slist_append(response_headers, hdr);
        }
	if (!response_headers)
		return result;

	*curl_headers = response_headers;

	curl_easy_setopt(curl_ctx, CURLOPT_HTTPHEADER, response_headers);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, write_cb);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, (void *)svc);

	return result = 0;
}
   

#define GCLOUD_AUTH_HEADER "Authorization: GoogleLogin auth="
int http_post_auth(gservice *svc, const char *url,
	      char *post_data, unsigned int length, ...) {

	struct curl_slist *response_headers = NULL;
	int result = -1;
	CURLcode res;
	CURL *curl_ctx;
        va_list headers;
        va_start(headers, length);

	char *auth;
	auth = malloc(strlen(GCLOUD_AUTH_HEADER) + strlen(svc->auth)+1);
	sprintf(auth, "Authorization: GoogleLogin auth=%s", svc->auth);

	const char *ctype   = "Content-Type: application/x-www-form-urlencoded";
	const char *charset = "Accept-Charset: UTF-8";

        const char *header_array[] = {auth, ctype, charset, NULL};

	curl_ctx = svc->curl;
	result = load_vheaders(svc, &response_headers, header_array, headers);
        va_end(headers);

	if (result) goto exit;

	/* It seems deprecated, as long I set POSTFIELDS */
	curl_easy_setopt(curl_ctx, CURLOPT_POST, 1);
	curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
	if (post_data) {
		curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDS, post_data);
		curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, length);
	}
	else
		curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, 0);

	res = curl_easy_perform(curl_ctx);
	result = check_request_error(svc, res, GCAL_DEFAULT_ANSWER);

	/* cleanup */
	curl_slist_free_all(response_headers);

	exit:
		return result;
}

int http_post(gservice *gcalobj, const char *url,
	      char *header, char *header2, char *header3,
	      char *header4,
	      char *post_data, unsigned int length,
	      const int expected_answer,
	      const char *gdata_version) {
	int result = -1;
	CURLcode res;
	struct curl_slist *response_headers = NULL;
	CURL *curl_ctx;
	if (!gcalobj)
		goto exit;

	curl_ctx = gcalobj->curl;
	result = common_upload(gcalobj, header, header2, header3, header4, &response_headers, gdata_version);
	if (result)
		goto exit;

	/* It seems deprecated, as long I set POSTFIELDS */
	curl_easy_setopt(curl_ctx, CURLOPT_POST, 1);
	curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
	if (post_data) {
		curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDS, post_data);
		curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, length);
	}
	else
		curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, 0);

	res = curl_easy_perform(curl_ctx);
	result = check_request_error(gcalobj, res, expected_answer);

	/* cleanup */
	curl_slist_free_all(response_headers);

exit:
	return result;

}

static int check_request_error(gservice *gcalobj, int code,
			       int expected_answer)
{
	int result = 0;
	CURL *curl_ctx = gcalobj->curl;

	curl_easy_getinfo(curl_ctx, CURLINFO_HTTP_CODE,
			  &(gcalobj->http_code));
	if (code || (gcalobj->http_code != expected_answer)) {

		if (gcalobj->curl_msg)
			free(gcalobj->curl_msg);

		gcalobj->curl_msg = strdup(curl_easy_strerror(code));

		if (gcalobj->fout_log)
			fprintf(gcalobj->fout_log, "%s\n%s%s\n%s%d\n",
				"check_request_error: failed request.",
				"Curl code: ", gcalobj->curl_msg,
				"HTTP code: ", (int)gcalobj->http_code);
		result = -1;
	}

	return result;
}

void clean_buffer(gservice *gcal_obj)
{
	if (gcal_obj) {
		memset(gcal_obj->buffer, 0, gcal_obj->length);
		gcal_obj->previous_length = 0;
	}
}


int gcal_get_authentication(gservice *gcalobj,
			    char *user, char *password)
{
	int post_len = 0;
	char *post = NULL;
	int result = -1;
	char *tmp = NULL;
	char *buffer = NULL;
	char *enc_user = NULL;
	char *enc_password = NULL;

	if (!gcalobj || !user || !password)
		goto exit;

	/* Must cleanup HTTP buffer between requests */
	clean_buffer(gcalobj);

	/* Properly encode user and password */
	enc_user = curl_easy_escape(gcalobj->curl, user, strlen(user));
	enc_password = curl_easy_escape(gcalobj->curl, password,
					strlen(password));
	if ((!enc_password) || (!enc_user))
		goto cleanup;

	post_len = strlen(enc_user) + strlen(enc_password) +
		   sizeof(ACCOUNT_TYPE) +
		   sizeof(EMAIL_FIELD) +
		   sizeof(PASSWD_FIELD) + sizeof(SERVICE_FIELD) +
		   strlen(gcalobj->service) + sizeof(CLIENT_SOURCE)
		   + 5; /* thanks to 4 '&' between fields + null character */
	post = (char *) malloc(post_len);
	if (!post)
		goto cleanup;

	snprintf(post, post_len - 1,
                 "%s&"
		 "%s%s&"
		 "%s%s&"
		 "%s%s&"
		 "%s",
                 ACCOUNT_TYPE,
		 EMAIL_FIELD, enc_user,
		 PASSWD_FIELD, enc_password,
		 SERVICE_FIELD, gcalobj->service,
		 CLIENT_SOURCE);

	result = http_post(gcalobj, GCLOUDAUTH_URL,
			   "Content-Type: application/x-www-form-urlencoded",
			   NULL, NULL, NULL, post, strlen(post),
			   GCAL_DEFAULT_ANSWER,
			   NULL);

	if ((tmp = strstr(user, "@"))) {
		if (!(buffer = strdup(user)))
			goto cleanup;

		buffer[tmp - user] = '\0';
		if (!(gcalobj->user = strdup(buffer)))
			goto cleanup;

		++tmp;
		if (!(gcalobj->domain = strdup(tmp)))
			goto cleanup;

		free(buffer);
	} else {
		gcalobj->user = strdup(user);
		gcalobj->domain = strdup("gmail.com");
	}

	if (result)
		goto cleanup;

	/* gcalendar server returns a string like this:
	 * SID=value\n
	 * LSID=value\n
	 * Auth=value\n
	 * and we only need the authorization token to login later
	 * without the '\r\n' in the end of string.
	 * TODO: move this to a distinct function and write utests.
	 */
	if (gcalobj->auth)
		free(gcalobj->auth);

	gcalobj->auth = strstr(gcalobj->buffer, HEADER_AUTH);
	gcalobj->auth = strdup(gcalobj->auth + strlen(HEADER_AUTH));
	if (!gcalobj->auth)
		goto cleanup;

	tmp = strstr(gcalobj->auth, "\n");
	if (tmp)
		*tmp = '\0';

	result = 0;

cleanup:
	if (enc_user)
	    curl_free(enc_user);
	if (enc_password)
	    curl_free(enc_password);
	if (post)
		free(post);

exit:
	return result;

}

static char* uniq_boundary() {
  static char boundary[256];
  static long next_counter = 0;
  time_t now = time(NULL);
  sprintf(boundary, "boundary.%s.%ld", asctime(localtime(&now)), next_counter++);
  return boundary;
}

static char* append_lines(char *buffer, char *lines) {
  //reallocate and merge
  int buffer_len = buffer? strlen(buffer) + 1:0;
  buffer_len += lines? strlen(lines) + 1:0;

  char *newbuffer = malloc(buffer_len);
  if(buffer && lines) {
    sprintf(newbuffer, "%s\r\n%s", buffer, lines);
  } else if (lines) {
    sprintf(newbuffer, "%s", lines);
  } else if (buffer) {
    return buffer;
  }

  if( buffer ) free(buffer);
  return newbuffer;
}

/**
 * this is built as
 * FIELD, name, value 
 * FILE, name, filename, filedata, filetype
 * END
 **/
char *encode_multipart(char *boundary, ...) {
  int  type;
  char *buffer = NULL;
  va_list arguments;
  va_start (arguments, boundary);

  char *name, *value, *data, *file_type;
  char newlines[1024];

  for( type = va_arg(arguments, int); type != 0; type = va_arg(arguments, int)) {
    name = va_arg(arguments, char*);
    value = va_arg(arguments, char*);
    if(type == 2) {
       file_type = va_arg(arguments, char*);
       data = va_arg(arguments, char*);
       sprintf(newlines, "--%s\r\n"
                      "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n"
                      "Content-Type: %s\r\n", boundary, name, value, file_type);
       buffer = append_lines(buffer, newlines);
       buffer = append_lines(buffer, data);
    } else {
       sprintf(newlines, "--%s\r\n"
                      "Content-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
		      boundary, name, value);
       buffer = append_lines(buffer, newlines);
    }
  }
  
  va_end(arguments);

  sprintf(newlines, "\n--%s--\n", boundary);
  buffer = append_lines(buffer, newlines);
  return buffer;
}

int get_url(gservice *gs, char* url, char *tokens[], struct curl_httppost *data, int anon) {
	int result = -1;
	CURLcode res;
	struct curl_slist *response_headers = NULL;
	CURL *curl_ctx = gs->curl;
	curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
        response_headers = curl_slist_append(response_headers, "Accept:");
	if(!anon) {
		char *auth;
		auth = malloc(strlen(GCLOUD_AUTH_HEADER) + strlen(gs->auth)+1);
		sprintf(auth, "Authorization: GoogleLogin auth=%s", gs->auth);
		response_headers = curl_slist_append(response_headers, auth);
		free(auth);
	}

	response_headers = curl_slist_append(response_headers, "X-CloudPrint-Proxy: api-prober");

	if(data) {
		curl_easy_setopt(curl_ctx, CURLOPT_POST, 1);
		curl_easy_setopt(curl_ctx, CURLOPT_HTTPPOST, data);
	} else {
		curl_easy_setopt(curl_ctx, CURLOPT_POST, 0);
		//curl_easy_setopt(curl_ctx, CURLOPT_POSTFIELDSIZE, 0);
        }
  
	curl_easy_setopt(curl_ctx, CURLOPT_HTTPHEADER, response_headers);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, write_cb);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, (void *)gs);
	res = curl_easy_perform(curl_ctx);

	result = check_request_error(gs, res, GCAL_DEFAULT_ANSWER);
	curl_slist_free_all(response_headers);

        return result;
}


int get_file(gservice *gs, char* url, char* filePath, int anon) {
	int result = -1;
	CURLcode res;
	struct curl_slist *response_headers = NULL;
	CURL *curl_ctx = gs->curl;

	struct remoteFile file={
    filePath,
    NULL
  };

	curl_easy_setopt(curl_ctx, CURLOPT_URL, url);
        response_headers = curl_slist_append(response_headers, "Accept:");
	if(!anon) {
		char *auth;
		auth = malloc(strlen(GCLOUD_AUTH_HEADER) + strlen(gs->auth)+1);
		sprintf(auth, "Authorization: GoogleLogin auth=%s", gs->auth);
		response_headers = curl_slist_append(response_headers, auth);
		free(auth);
	}

	response_headers = curl_slist_append(response_headers, "X-CloudPrint-Proxy: api-prober");

	curl_easy_setopt(curl_ctx, CURLOPT_HTTPHEADER, response_headers);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEFUNCTION, write_file);
	curl_easy_setopt(curl_ctx, CURLOPT_WRITEDATA, (void *)&file);
	res = curl_easy_perform(curl_ctx);
	curl_slist_free_all(response_headers);
	if(file.stream) {
		fclose(file.stream);
	}
  return result;
}
